// java/com/dbms/execution/execution/IndexNestedLoopJoinExecutor.java
package com.dbms.execution.execution;

import com.dbms.catalog.Catalog;
import com.dbms.common.RID;
import com.dbms.common.Schema;
import com.dbms.common.Tuple;
import com.dbms.execution.plan.PlanNode;
import com.dbms.storage.BufferPoolManager;
import com.dbms.storage.Page;
import com.dbms.storage.TablePage;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * IndexNestedLoopJoinExecutor (索引嵌套循环连接执行器) 使用索引来加速内表的查找。
 * 与普通的嵌套循环连接（需要对内表进行全表扫描）不同，该算法的效率极高：
 * 1. 从外表 (outerExecutor) 取出一条元组。
 * 2. 从该元组中抽取出连接键 (join key) 的值。
 * 3. 【核心优化】使用这个键值直接在内表的索引中进行快速查找 (O(logN) 或 O(1) 复杂度)，
 * 而不是扫描整个内表 (O(N) 复杂度)。
 * 4. 如果索引找到了匹配的记录，则获取该记录并与外表元组合并，然后返回。
 * 5. 重复此过程，直到外表遍历完毕。
 */
public class IndexNestedLoopJoinExecutor implements Executor {

    /**
     * 外表执行器。
     */
    private final Executor outerExecutor;
    /**
     * 内表的元数据，包含了对内表索引的引用。
     */
    private final Catalog.TableMetadata innerTableInfo;
    /**
     * 缓冲池管理器，用于根据 RID 获取内表的元组数据。
     */
    private final BufferPoolManager bufferPoolManager;
    /**
     * 外表元组中，作为连接键的列的索引。
     */
    private final int outerJoinKeyIndex;
    /**
     * 连接操作的输出模式。
     */
    private final Schema outputSchema;

    /**
     * 保存当前从外表中取出的元组。
     */
    private Tuple outerTuple = null;

    public IndexNestedLoopJoinExecutor(Executor outerExecutor, Catalog.TableMetadata innerTableInfo,
                                       int outerJoinKeyIndex, BufferPoolManager bpm, Schema outputSchema) {
        this.outerExecutor = outerExecutor;
        this.innerTableInfo = innerTableInfo;
        this.outerJoinKeyIndex = outerJoinKeyIndex;
        this.bufferPoolManager = bpm;
        this.outputSchema = outputSchema;
    }

    @Override
    public void open() {
        outerExecutor.open();
        this.outerTuple = null;
    }

    @Override
    public Tuple next() {
        try {
            // 循环直到找到一个匹配的连接元组，或者外表已经处理完毕
            while (true) {
                // 1. 从外表获取一条记录
                outerTuple = outerExecutor.next();
                if (outerTuple == null) {
                    return null; // 外表已经遍历完，连接结束
                }

                // 2. 从外表记录中抽取出连接键 (我们假设连接键是Integer类型)
                Integer joinKey = (Integer) outerTuple.getValue(outerJoinKeyIndex);

                // 3. 【核心优化】使用内表的索引查找匹配记录的 RID
                RID innerRid = innerTableInfo.index.find(joinKey);

                // 4. 如果在内表的索引中找到了匹配的记录
                if (innerRid != null) {
                    // 4a. 根据 RID 从磁盘获取内表记录的页面
                    Page page = bufferPoolManager.fetchPage(innerRid.getPageId());
                    TablePage tablePage = new TablePage(page);
                    byte[] tupleData = tablePage.getTuple(innerRid.getSlotNum());
                    // 解钉页面，因为我们只是读取，所以页面不是脏的
                    bufferPoolManager.unpinPage(page.getPageId(), false);
                    Tuple innerTuple = new Tuple(tupleData, innerTableInfo.schema);

                    // 4b. 将内外表的元组合并成一个新的结果元组
                    List<Object> combinedValues = new ArrayList<>();
                    for (int i = 0; i < outerExecutor.getSchema().getColumnCount(); i++) {
                        combinedValues.add(outerTuple.getValue(i));
                    }
                    for (int i = 0; i < innerTableInfo.schema.getColumnCount(); i++) {
                        combinedValues.add(innerTuple.getValue(i));
                    }

                    // 找到了一个匹配，返回它。下一次调用 next() 将会从外表的下一条记录继续。
                    return new Tuple(combinedValues, outputSchema);
                }
                // 如果没找到匹配的内表记录，则外层 while 循环会继续，从外表获取下一条记录
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void close() {
        outerExecutor.close();
    }

    @Override
    public Schema getSchema() {
        return outputSchema;
    }

    @Override
    public PlanNode getPlanNode() {
        return null;
    }
}